package no.uib.hplims.jobs;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;

import no.uib.hplims.models.BlastInfo;
import no.uib.hplims.models.BlastSearch;
import no.uib.hplims.tools.events.BlastResultFetchedEvent;
import no.uib.hplims.tools.events.BlastResultsFetchedListener;

import org.vaadin.appfoundation.persistence.facade.FacadeFactory;

import uk.ac.ebi.kraken.interfaces.blast.Hit;
import uk.ac.ebi.kraken.interfaces.blast.LocalAlignment;
import uk.ac.ebi.kraken.interfaces.uniprot.UniProtEntry;
import uk.ac.ebi.kraken.interfaces.uniprot.description.FieldType;
import uk.ac.ebi.kraken.interfaces.uniprot.features.Feature;
import uk.ac.ebi.kraken.interfaces.uniprot.features.FeatureLocation;
import uk.ac.ebi.kraken.interfaces.uniprot.features.FeatureType;
import uk.ac.ebi.kraken.model.blast.JobStatus;
import uk.ac.ebi.kraken.uuw.services.remoting.EntryRetrievalService;
import uk.ac.ebi.kraken.uuw.services.remoting.UniProtJAPI;
import uk.ac.ebi.kraken.uuw.services.remoting.UniProtQueryService;
import uk.ac.ebi.kraken.uuw.services.remoting.blast.BlastData;
import uk.ac.ebi.kraken.uuw.services.remoting.blast.BlastHit;

/**
 * 
 * @author John
 */
public class Blaster implements Callable<Object> {

	// Create the listener list
	protected javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();

	public Blaster() {
	}

	public Object call() throws Exception {
		run();
		return null;
	}

	private void run() {
		// Get all unfinished searches from DB
		List<BlastSearch> unfinishedSearches = FacadeFactory.getFacade().list(
				BlastSearch.class);

		Iterator<BlastSearch> it = unfinishedSearches.iterator();
		while (it.hasNext()) {
			BlastSearch bs = it.next();

			String jobID = bs.getJobID();
			String querySequence = bs.getQuery();
			String species = bs.getSpecies();

			ArrayList<BlastInfo> results = getJobResult(jobID, querySequence, species);

			// Remove BlastSearch from list
			it.remove();
			// Delete BlastSearch from DB
			FacadeFactory.getFacade().delete(bs);

			fireBlastResultsFetchedEvent(new BlastResultFetchedEvent(this,
					querySequence, results, bs.getOrder(), bs.getSearchMode(), species));

			try {
				// Sleep before next request
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	private ArrayList<BlastInfo> getJobResult(String jobID, String querySequence, String species) {
		// Get the UniProt Service. This is how to access the blast service
		UniProtQueryService service = UniProtJAPI.factory
				.getUniProtQueryService();

		// Sleep until UniProt has finished the job
		while (service.checkStatus(jobID) != JobStatus.FINISHED) {
			try {
				Thread.sleep(4000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// When the job has finished
		if (service.checkStatus(jobID) == JobStatus.FINISHED) {

			ArrayList<BlastInfo> resultList = new ArrayList<BlastInfo>();
			System.out.println("job " + jobID + " has finished");
			// Get job results
			BlastData<UniProtEntry> result = service.getResults(jobID);
			// Get list of BlastHits from result
			List<BlastHit<UniProtEntry>> blastHits = result.getBlastHits();

			// Go through list of BlastHits
			for (BlastHit<UniProtEntry> bh : blastHits) {
				Hit hit = bh.getHit();

				// Create entry retrival service to fetch correct UniProtEntry
				// object
				EntryRetrievalService entryRetrievalService = UniProtJAPI.factory
						.getEntryRetrievalService();
				UniProtEntry entry = null;
				int attempts = 0;
				while (entry == null && attempts < 25) {
					// Retrieve UniProt entry by its accession number
					entry = (UniProtEntry) entryRetrievalService
							.getUniProtEntry(hit.getAc());
					attempts++;
					try {
						Thread.sleep(250);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}

				// Check if hit exists
				if (hit != null) {
					// Check if the hit belongs to selected species
					if (hit.getDescription().contains(species)) {

						if (!hit.getAlignments().isEmpty()) {
							for (LocalAlignment la : hit.getAlignments()) {
								if ((la.getIdentity() == 100)
										&& (la.getMatchSeq()
												.equals(querySequence))) {
									// Check if entry exists
									if (entry != null) {
										BlastInfo bi = convertToBlastInfo(hit, entry);
										if (bi != null) {
											resultList.add(bi);
										}
									}
								}
							}
						}
					}
				}
			} // end for
			return resultList;
		}
		return new ArrayList<BlastInfo>();
	}

	private BlastInfo convertToBlastInfo(Hit hit, UniProtEntry entry) {
		if (hit != null && entry != null) {

			BlastInfo blastInfo = new BlastInfo();
			// Extract needed info from BlastHit
			String uniProtAccessionNumber = hit.getAc();
			String fullProteinName = "";
			if (entry.getProteinDescription().hasRecommendedName()) {
				fullProteinName = entry.getProteinDescription()
						.getRecommendedName().getFieldsByType(FieldType.FULL)
						.get(0).getValue();
			} else if (entry.getProteinDescription().hasAlternativeNames()) {
				fullProteinName = entry.getProteinDescription()
						.getAlternativeNames().get(0)
						.getFieldsByType(FieldType.FULL).get(0).getValue();
			} else {
				fullProteinName = "No recommended name";
			}

			String proteinShortName = entry.getUniProtId().getValue();
			int peptideStart = hit.getAlignments().get(0).getStartMatchSeq();
			int peptideEnd = hit.getAlignments().get(0).getEndMatchSeq();

			// TODO: Add more dynamic feature type list. Persist wanted
			// types.

			/*
			 * Get all the features (known modifications) of a protein. Check if
			 * they appply to our peptide.
			 */
			ArrayList<Feature> featureList = new ArrayList<Feature>();
			for (Feature f : entry.getFeatures()) {

				if (f.getType() == FeatureType.MOD_RES
						|| f.getType() == FeatureType.SITE
						|| f.getType() == FeatureType.VARIANT) {

					FeatureLocation fl = f.getFeatureLocation();

					if (fl.getStart() >= peptideStart
							&& fl.getStart() <= peptideEnd) {
						featureList.add(f);
					}
				}

			}

			// init blastInfo:
			blastInfo.setUniProtAccessionNumber(uniProtAccessionNumber);
			blastInfo.setProteinName(fullProteinName);
			blastInfo.setProteinShortName(proteinShortName);
			blastInfo.setPeptideStart(peptideStart);
			blastInfo.setPeptideEnd(peptideEnd);
			blastInfo.setKnownModifications(featureList);

			System.out.println("UPA# " + blastInfo.getUniProtAccessionNumber());
			System.out.println("Name: " + blastInfo.getProteinName());
			System.out
					.println("Short name: " + blastInfo.getProteinShortName());
			System.out.println("Start: " + blastInfo.getPeptideStart());
			System.out.println("End: " + blastInfo.getPeptideEnd());
			System.out.println("Mods: " + blastInfo.getKnownModifications());

			return blastInfo;
		}

		return null;
	}

	// This methods allows classes to register for BlastResultFetchedEvents
	public void addListener(BlastResultsFetchedListener listener) {
		listenerList.add(BlastResultsFetchedListener.class, listener);
	}

	// This methods allows classes to unregister for BlastResultFetchedEvents
	public void removeListener(BlastResultsFetchedListener listener) {
		listenerList.remove(BlastResultsFetchedListener.class, listener);
	}

	// This private class is used to fire BlastResultFetchedEvents
	void fireBlastResultsFetchedEvent(BlastResultFetchedEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == BlastResultsFetchedListener.class) {
				((BlastResultsFetchedListener) listeners[i + 1])
						.eventOccurred(evt);
			}
		}
	}
}
